package de.lmu.mvs.lerngruppenfinder.utils;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.StringRequest;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.google.common.base.Joiner;

import android.app.Activity;
import android.util.Log;

/**
 * Manages all requests and answers from our server.
 * 
 * @author Christopher Gebhardt
 *
 */
public class ServerConnector {

	private static final String TAG = ServerConnector.class.getSimpleName();

	public static final String BASE_URL = "http://lgf.aw.gl/";
	public static final String API_URL = "api.php";
	public static final String URL = BASE_URL + API_URL;

	public interface ServerConnectorListener<T> {

		public void requestFinished(T json);

		public void requestError(ServerError error);

	}

	public enum ServerError {
		UNKNOWN_USER{
			@Override
			public String getErrorMessage(){
				return "No user found with this email password combination.";
			}
		},
		DUPLICATE {
			@Override
			public String getErrorMessage() {
				return "There is already a user with this email in the database.";
			}
		},
		UNKNOWN_ERROR {
			@Override
			public String getErrorMessage() {
				return "An unknown error occured.";
			}
		},
		NO_ACTION_SELECTED {
			@Override
			public String getErrorMessage() {
				return "Missing or unknown action for this request.";
			}
		},
		JSON_PARSE_ERROR {
			@Override
			public String getErrorMessage() {
				return "Error parsing json response from server.";
			}
		},
		NOT_ENOUGH_ARGUMENTS {
			@Override
			public String getErrorMessage() {
				return "Missing or unknown action for this request.";
			}
		},
		DUBLICATE_ABO {
			@Override
			public String getErrorMessage() {
				return "Dublicate Abo";
			}
		},
		ALREADY_CHECKED_IN {
			@Override
			public String getErrorMessage() {
				return "You are already checked in.";
			}
		},
		NO_USERS_CHECKED_IN {
			@Override
			public String getErrorMessage() {
				return "No Users checked in";
			}
		},
		WRONG_PASSWORD {
			@Override
			public String getErrorMessage() {
				return "Wrong password entered.";
			}
		},
		JSON_IS_NULL {
			@Override
			public String getErrorMessage() {
				return "Json returned no results.";
			}
		};
		
		public abstract String getErrorMessage();
	}

	/**
	 * Login user at server
	 * 
	 * @param listener
	 * @param email
	 * @param password
	 */
	public static void login(final ServerConnectorListener<JsonObject> listener, String email, String password){
		try {
			byte[] sha256 = MessageDigest.getInstance("SHA256").digest(password.getBytes("UTF-8"));
			
			HashMap<String, String> params = new HashMap<String, String>();
			params.put("action", "login");
			params.put("mail", email);
			params.put("pass", new String(new BigInteger(1, sha256).toString(16)));
			
			sendRequest(listener, URL, params);
			
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Register user at server
	 * 
	 * @param listener
	 * @param name
	 * @param email
	 * @param password
	 */
	public static void register(final ServerConnectorListener<JsonObject> listener, String name, String email, String password){
		try {
			byte[] sha256 = MessageDigest.getInstance("SHA256").digest(password.getBytes("UTF-8"));

			HashMap<String, String> params = new HashMap<String, String>();
			params.put("action", "register");
			params.put("name", name);
			params.put("mail", email);
			params.put("pass", new String(new BigInteger(1, sha256).toString(16)));

			sendRequest(listener, URL, params);

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Reset the password of a user with the given mail. The password is replaced by
	 * a random array of chars
	 * 
	 * @param listener
	 * @param email
	 */
	public static void resetPassword(final ServerConnectorListener<JsonObject> listener, String email){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "resetPW");
		params.put("mail", email);

		sendRequest(listener, URL, params);
	}

	/**
	 * Get user by id
	 * 
	 * @param listener
	 * @param userId
	 */
	public static void getUserById(final ServerConnectorListener<JsonObject> listener, int userId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getUser");
		params.put("userID", userId+"");

		sendRequest(listener, URL, params);
	}

	/**
	 * Get an event by its id FIXME server debugging, returns null
	 * 
	 * @param listener
	 * @param eventId
	 */
	public static void getGroupById(final ServerConnectorListener<JsonObject> listener, int eventId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getVeranstaltungID");
		params.put("veranstaltungID", eventId+"");

		sendRequest(listener, URL, params);
	}

	/**
	 * Get all events a user is subscribed to
	 * 
	 * @param listener
	 * @param userId
	 */
	public static void getSubscribedGroups(final ServerConnectorListener<JsonArray> listener, int userId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getAbbonierteVeranstaltung");
		params.put("userID", userId+"");

		sendRequest(listener, URL, params);
	}

	/**
	 * Subscribe to a event
	 * FIXME Test and Template
	 * 
	 * @param listener
	 * @param userId
	 * @param eventId
	 */
	public static void subscribeToGroup(final ServerConnectorListener<JsonObject> listener, int userId, int eventId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "abboniereVeranstaltung");
		params.put("userID", userId+"");
		params.put("veranstaltungID", eventId+"");

		sendRequest(listener, URL, params);
	}

	/**
	 * Get all events in a radius [meter] around a geo location
	 * 
	 * @param listener
	 * @param latitude
	 * @param longitude
	 * @param radius in meter
	 */
	public static void getGroupsInRadius(final ServerConnectorListener<JsonArray> listener, double latitude, double longitude, double radius){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getVeranstaltungenUmkreis");
		params.put("gpsX", latitude+"");
		params.put("gpsY", longitude+"");
		params.put("radius", radius+"");

		sendRequest(listener, URL, params);
	}
	
	/**
	 * Get all buildings in the given radius
	 * 
	 * @param listener
	 * @param latitude
	 * @param longitude
	 * @param radius in meter
	 */
	public static void getBuildingsInRadius(final ServerConnectorListener<JsonArray> listener, double latitude, double longitude, double radius){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getBuildingsClose");
		params.put("gpsX", latitude+"");
		params.put("gpsY", longitude+"");
		params.put("radius", radius+"");

		sendRequest(listener, URL, params);
	}

	/**
	 * Get an array with all interests (tags to filter an event)
	 * 
	 * @param listener
	 */
	public static void getInterests(final ServerConnectorListener<JsonArray> listener){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getInteressen");

		sendRequest(listener, URL, params);
	}
	
	/**
	 * Get a specific interest by its id
	 * 
	 * @param listener
	 * @param interest
	 */
	public static void getInterestById(final ServerConnectorListener<JsonArray> listener, int interest){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getInteresseID");
		params.put("interestID", interest+"");
		
		sendRequest(listener, URL, params);
	}
	
	/**
	 * Change password of user
	 * 
	 * @param listener
	 * @param userId
	 * @param oldPassword
	 * @param newPassword
	 */
	public static void changeUserPassword(final ServerConnectorListener<JsonObject> listener, int userId, String oldPassword, String newPassword){
		try {
			byte[] sha256old = MessageDigest.getInstance("SHA256").digest(oldPassword.getBytes("UTF-8"));
			byte[] sha256new = MessageDigest.getInstance("SHA256").digest(newPassword.getBytes("UTF-8"));
			
			Log.d(TAG, new String(new BigInteger(1, sha256old).toString(16)));
			
			HashMap<String, String> params = new HashMap<String, String>();
			params.put("action", "changePass");
			params.put("userID", userId+"");
			params.put("oldPW", new String(new BigInteger(1, sha256old).toString(16)));
			params.put("newPW", new String(new BigInteger(1, sha256new).toString(16)));
			
			sendRequest(listener, URL, params);

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Change name of user
	 * 
	 * @param listener
	 * @param userId
	 * @param name
	 */
	public static void changeUserName(final ServerConnectorListener<JsonObject> listener, int userId, String name){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "changeName");
		params.put("userID", userId+"");
		params.put("newName", name+"");
		
		sendRequest(listener, URL, params);
	}

	/**
	 * Get all interests of a user
	 * 
	 * @param listener
	 * @param userId
	 */
	public static void getUserInterests(final ServerConnectorListener<JsonArray> listener, int userId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getUserInteressen");
		params.put("userID", userId+"");
		sendRequest(listener, URL, params);
	}
	
	/**
	 * Set interests for user with userId. Interest are given by its id
	 * @param listener
	 * @param userId
	 * @param interests
	 */
	public static void setUserInterests(final ServerConnectorListener<JsonArray> listener, int userId, List<Integer> interests){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "setUserInteressen");
		params.put("userID", userId+"");
		if(interests == null || (interests.size() == 1 && interests.contains(0))){
			params.put("interests", Joiner.on(",").join(interests));
		} else {
			params.put("interests", Joiner.on(",").join(interests));
		}
		
		sendRequest(listener, URL, params);
	}
	
	public static void getComments(final ServerConnectorListener<JsonArray> listener, int eventID){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getComments");
		params.put("eventID", eventID+"");
		sendRequest(listener, URL, params);
	}
	
	public static void createPoll(final ServerConnectorListener<JsonArray> listener, int userID, int recipientID, String content){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "createPoll");
		params.put("userID", userID+"");
		params.put("recipientID", userID+"");
		params.put("content", userID+"");		
		sendRequest(listener, URL, params);
	}
	
	
	public static void contactUser(final ServerConnectorListener<JsonObject> listener, int userID, int recipientID, String content){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "contactUser");
		params.put("recipientID", recipientID+"");
		params.put("content", content);
		params.put("userID", userID+"");
		sendRequest(listener, URL, params);

	}	
	
	public static void getPoll(final ServerConnectorListener<JsonArray> listener, int pollID){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getPoll");
		params.put("pollID", pollID+"");
		sendRequest(listener, URL, params);
	}
	
	
	//@TODO
	public static void vote(final ServerConnectorListener<JsonArray> listener, int pollId, int userId, int decicion){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "vote");
		params.put("userID", userId+"");
		params.put("pollID", pollId+"");
		params.put("decicion", decicion+"");
		sendRequest(listener, URL, params);
	}
	
	//@TODO
	public static void createEvent(final ServerConnectorListener<JsonObject> listener, String name,String description,int fieldOfStudy, int building, String date, String expires, double gpsX, double gpsY){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "createEvent");
		params.put("name", name+"");
		params.put("beschreibung", description+"");
		params.put("fachgebiet", fieldOfStudy+"");
		params.put("building", building+"");
		params.put("date", date+"");
		params.put("expires", expires+"");
		params.put("gpsX", gpsX+"");
		params.put("gpsY", gpsY+"");
		sendRequest(listener, URL, params);
	}
	
	//@TODO
	public static void comment(final ServerConnectorListener<JsonArray> listener, int userId, int eventId, String comment, int pollId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "comment");
		params.put("eventID", eventId+"");
		params.put("comment", comment+"");
		params.put("pollID", pollId+"");
		params.put("userID", userId+"");
		sendRequest(listener, URL, params);
	}
	
	//@TODO
	public static void getEventFiles(final ServerConnectorListener<JsonArray> listener,int eventId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getEventFiles");
		params.put("eventID", eventId+"");
		sendRequest(listener, URL, params);
	}
	
	
	//@TODO
	public static void checkinToEvent(final ServerConnectorListener<JsonObject> listener,int eventId, int userId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "checkinToEvent");
		params.put("eventID", eventId+"");
		params.put("userID", userId+"");
		sendRequest(listener, URL, params);
	}

	
	//@TODO
	public static void getUsersCheckedIn(final ServerConnectorListener<JsonArray> listener,int eventId){
		HashMap<String, String> params = new HashMap<String, String>();
		params.put("action", "getUsersCheckedIn");
		params.put("eventID", eventId+"");
		sendRequest(listener, URL, params);
	}
	
	//@TODO
	public static void uploadEventFile(ServerConnectorListener<JsonObject> listener, int eventId, File file){
		HashMap<String,String> params = new HashMap<String, String>();
		params.put("action","uploadEventFile");
		params.put("eventID",eventId+"");
		uploadFile(listener, file, params);
	}
			
	private static void sendRequest(final ServerConnectorListener listener, String url, final HashMap<String, String> params){

		if(listener == null){
			throw new IllegalArgumentException("listener must not be null");
		}

		Log.i(TAG, "POST Request to " +url+" with parameters "+params.toString()+" ...");

		StringRequest req = new StringRequest(
				Request.Method.POST, url,

				new Response.Listener<String>() {

					@Override
					public void onResponse(String response) {
						Log.d(TAG, response);
						
						if(isJsonObject(response)){
							// if its a jsonobject there could be an error message inside.
							parseJsonObject(listener, JsonObject.readFrom(response));
						} else if(isJsonArray(response)){
							// if it can be parsed as jsonarray everything is fine
							listener.requestFinished(JsonArray.readFrom(response));
						} else {
							listener.requestError(ServerError.JSON_PARSE_ERROR);
						}
					}
				}, 
				new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						/*
						 * found a connection or server-error
						 */
						if (error.getMessage() != null) {
							VolleyLog.e("Error: ", error.getMessage());
						}
						listener.requestError(ServerError.UNKNOWN_ERROR);
					}
				}){

			@Override
			protected Map<String,String> getParams(){
				return params;
			}

			@Override
			public Map<String, String> getHeaders() throws AuthFailureError {
				Map<String,String> params = new HashMap<String, String>();
				params.put("Content-Type","application/x-www-form-urlencoded");
				return params;
			}
		};
		ApplicationController.getInstance().addToRequestQueue(req);
	}
	
	public static void uploadProfilePicture(ServerConnectorListener<JsonObject> listener, int userId, File file){
		HashMap<String,String> params = new HashMap<String, String>();
		params.put("action","uploadProfilePicture");
		params.put("userID",userId+"");
		uploadFile(listener, file, params);
	}
	
	/**
	 * Upload a file to server with mulitpart/form-data content type
	 * @param listener
	 * @param file
	 * @param params Hashmap with with further post parameters
	 */
	private static void uploadFile(final ServerConnectorListener<JsonObject> listener, 
			final File file, final HashMap<String, String> params){
		
		if(file == null || !file.exists()){
			Log.d(TAG, "Parameter file is null or does not exist.");
			listener.requestError(ServerError.NOT_ENOUGH_ARGUMENTS);
		}
        
		Thread uploadThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				HttpClient client = new DefaultHttpClient();
		        HttpPost post = new HttpPost(ServerConnector.URL);
		        
		        MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create()
		                .addPart("file", new FileBody(file));
		        for (Map.Entry<String, String> entry : params.entrySet()) {
					multipartEntity.addPart(entry.getKey(), new StringBody(entry.getValue(), ContentType.TEXT_PLAIN));
				}
		        HttpEntity reqEntity = multipartEntity.build();
		        post.setEntity(reqEntity);
		        
		        String response = "";
				try {
					response = EntityUtils.toString(client.execute(post).getEntity());
				} catch (ParseException e) {
					e.printStackTrace();
					listener.requestError(ServerError.UNKNOWN_ERROR);
				} catch (ClientProtocolException e) {
					e.printStackTrace();
					listener.requestError(ServerError.UNKNOWN_ERROR);
				} catch (IOException e) {
					e.printStackTrace();
					listener.requestError(ServerError.UNKNOWN_ERROR);
				}
				
				if(response != "" && response != null){
					if(isJsonObject(response)){
						parseJsonObject(listener, JsonObject.readFrom(response));
					} else if(isJsonArray(response)){
						listener.requestFinished(JsonObject.readFrom(response));
					}
					
					else {
						listener.requestError(ServerError.JSON_PARSE_ERROR);
					}
				}
				client.getConnectionManager().shutdown();
			}
		});
        uploadThread.start();
	}
	
	private static void parseJsonObject(final ServerConnectorListener listener, JsonObject json){
		try {
			// see if we have an error
			if(json.get("errorcode") != null){
				String error = json.get("errorcode").asString();
				
				ServerError serverError = null;
				if((serverError = responseIsError(error)) != null){
					// found server-processing error
					Log.w(TAG, "...error found!");
					listener.requestError(serverError);
					return;
				}
			}
			
			if(json == null){
				listener.requestError(ServerError.JSON_IS_NULL);
			}
			// if we came until here, there are no errors left
			Log.i(TAG, "...success!");
			Log.d(TAG, json.toString());
			
			listener.requestFinished(json);
		} catch (com.eclipsesource.json.ParseException e) {
			// String could not be parsed to json
			Log.e(TAG, e.getLocalizedMessage());
			listener.requestError(ServerError.JSON_PARSE_ERROR);
		} catch (UnsupportedOperationException e){
			// String could not be parsed to JsonObject
			Log.e(TAG, e.getLocalizedMessage());
			listener.requestError(ServerError.JSON_PARSE_ERROR);
		}
	}
	
	/**
	 * Checks if string is parsable as an json object
	 * @param string
	 * @return
	 */
	private static boolean isJsonObject(String string){
		try {
			JsonObject.readFrom(string);
			return true;
		} catch (com.eclipsesource.json.ParseException e) {
			return false;
		} catch (UnsupportedOperationException e){
			return false;
		}
	}
	
	/**
	 * Checks if string is parsable as an json array
	 * @param string
	 * @return
	 */
	private static boolean isJsonArray(String string){
		try {
			JsonArray.readFrom(string);
			return true;
		} catch (com.eclipsesource.json.ParseException e) {
			return false;
		} catch (UnsupportedOperationException e){
			return false;
		}
	}

	/**
	 * Check if string response is an error and return the according ServerError.
	 * Else return null;
	 * 
	 * @param error
	 * @return
	 */
	private static ServerError responseIsError(String error){
		if(error.equalsIgnoreCase("unknownError")){
			return ServerError.UNKNOWN_ERROR;
		}
		else if(error.equalsIgnoreCase("dublicate")){
			return ServerError.DUPLICATE;
		}
		else if(error.equalsIgnoreCase("unknownUserPassCombination")){
			return ServerError.UNKNOWN_USER;
		}
		else if(error.equalsIgnoreCase("wrongUserPassCombination")){
			return ServerError.WRONG_PASSWORD;
		}
		else if(error.equalsIgnoreCase("noActionSelected")){
			return ServerError.NO_ACTION_SELECTED;
		}
		else if(error.equalsIgnoreCase("dublicateAbo")){
			return ServerError.DUBLICATE_ABO;
		}
		else if(error.equalsIgnoreCase("alreadyCheckedIn")){
			return ServerError.ALREADY_CHECKED_IN;
		}	
		else if(error.equalsIgnoreCase("noUsersCheckedIn")){
				return ServerError.NO_USERS_CHECKED_IN;
		}
		else {
			return null;
		}
	}
	
	/**
	 * Load user info from server and store in client
	 * @param activity
	 * @param id
	 */
	public void loadUserCredentialsInStorage(final Activity activity, int id){
		getUserById(new ServerConnectorListener<JsonObject>() {

			@Override
			public void requestFinished(JsonObject json) {
				try {
					FileApi.writeJsonToStorage(json, Constants.USER_CREDENTIALS, activity);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			@Override
			public void requestError(ServerError error) {
				Log.e(TAG, error.getErrorMessage());
			}
		}, id);
	}
}

